﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;


namespace Typo3ImageUpload
{
    class Exiv2
    {
        #region Exiv2ImportedFunctions
        /// <summary>
        /// 
        /// </summary>
        /// <param name="src"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "OpenFileImage", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern int OpenFileImage(String src);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="key"></param>
        /// <param name="buffer"></param>
        /// <param name="bufsize"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "ReadMeta", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern int ReadMeta(int handle, string key, byte[] buffer, int bufsize);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="key"></param>
        /// <param name="buffer"></param>
        /// <param name="bufsize"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "SetThumbnail", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern void SetThumbnail(int handle, byte[] buffer, uint bufsize);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="buffer"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "GetThumbnail", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern uint GetThumbnail(int handle, byte[] buffer, uint bufsize);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="buffer"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "ImageData", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern uint ImageData(int handle, byte[] buffer, uint bufsize);
        /// <summary>
        /// DLL Type ID
        /// </summary>
        private enum DllTypeId
        {
            invalidTypeId, unsignedByte, asciiString, unsignedShort,
            unsignedLong, unsignedRational, invalid6, undefined,
            signedShort, signedLong, signedRational,
            stringType, isoDate, isoTime, Comment, Directory,
            lastTypeId
        };
        /// <summary>
        /// Modifies Metadata
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="key"></param>
        /// <param name="buffer"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "ModifyMeta", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern int ModifyMeta(int handle, string key, string buffer, DllTypeId type);
        /// <summary>
        /// Adds Metadata
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="key"></param>
        /// <param name="buffer"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "AddMeta", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern int AddMeta(int handle, string key, string buffer, DllTypeId type);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle"></param>
        /// <param name="key"></param>
        /// <param name="buffer"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "RemoveMeta", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern int RemoveMeta(int handle, string key);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "SaveImage", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern int SaveImage(int handle);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="handle"></param>
        /// <returns></returns>
        [DllImport("EXIVSIMPLE.DLL", EntryPoint = "FreeImage", SetLastError = true,
  CharSet = CharSet.Ansi, ExactSpelling = true,
  CallingConvention = CallingConvention.StdCall)]
        private static extern void FreeImage(int handle);
        #endregion
        #region Fields
        /// <summary>
        /// The current file handle
        /// </summary>
        private int _CurrentHandle = 0;
        /// <summary>
        /// The Ascii prefix of Exiv2 (?)
        /// </summary>
        private const string _CommentAsciiPrefix = "charset=\"Ascii\"";
        /// <summary>
        /// Cached Thumbnail
        /// </summary>
        private Image _Thumbnail = null;
        /// <summary>
        /// Cached Thumbnail
        /// </summary>
        private Image _Picture = null;
        /// <summary>
        /// Cached Time
        /// </summary>
        private DateTime? _CreationTime = null;
        /// <summary>
        /// Cached Time
        /// </summary>
        private DateTime? _ChangedTime = null;
        /// <summary>
        /// Cached comments
        /// </summary>
        private string _Comments = null;
        /// <summary>
        /// Cached descriptions
        /// </summary>
        private string _Description = null;
        /// <summary>
        /// Image buffer - static, good for memory, bad for multi threading (which is not used)
        /// </summary>
        private static byte[] _ImageBuffer;
        #endregion
        #region Properties
        /// <summary>
        /// Is the data loaded and valid?
        /// </summary>
        public bool DataValid
        {
            get
            {
                return (_CurrentHandle != 0);
            }
        }
        /// <summary>
        /// The picture data
        /// </summary>
        public Image Picture
        {
            get
            {
                if (_Picture != null)
                {
                    return _Picture;
                }

                if (_CurrentHandle != 0)
                {
                    if (_ImageBuffer == null)
                    { // 10MB... should be large enough...
                        _ImageBuffer = new byte[10000000];
                    }

                    uint len = ImageData(_CurrentHandle, _ImageBuffer, (uint)_ImageBuffer.Length);


                    if (len > 0)
                    {
                        MemoryStream s = new MemoryStream(_ImageBuffer, 0, (int)len, false);
                        if (s != null)
                        {
                            _Picture = Image.FromStream(s);
                            return _Picture;
                        }
                    }
                }
                return null;
            }
        }
        /// <summary>
        /// The thumbnail in the Exif Data
        /// </summary>
        public Image Thumbnail
        {
            get
            {
                if (_CurrentHandle != 0)
                {
                    if (_Thumbnail != null)
                    {
                        return _Thumbnail;
                    }

                    byte[] buffer = new byte[60000];
                    uint len = GetThumbnail(_CurrentHandle, buffer, (uint)buffer.Length);

                    if (len > 0)
                    {
                        byte[] pic = new byte[len];
                        for (int i = 0; i < len; i++)
                        {
                            pic[i] = buffer[i];
                        }

                        MemoryStream s = new MemoryStream(pic, 0, pic.Length, false);
                        if (s != null)
                        {
                            _Thumbnail = Image.FromStream(s);
                            return _Thumbnail;
                        }
                    }
                }
                _Thumbnail = null;
                return null;

            }
            set
            {
                if (value != null)
                {
                    MemoryStream s = new MemoryStream();
                    value.Save(s, ImageFormat.Jpeg);

                    byte[] img = s.ToArray();

                    if (_CurrentHandle != 0)
                    {
                        SetThumbnail(_CurrentHandle, img, (uint)img.Length);
                    }
                    _Thumbnail = value;
                }
            }
        }
        /// <summary>
        /// Gets the creation Time
        /// </summary>
        /// <remarks>"YYYY:MM:DD HH:MM:SS"+0x00</remarks>
        public DateTime CreationTime
        {
            get
            {
                if (_CurrentHandle == 0)
                {
                    return DateTime.MinValue;
                }
                if (_CreationTime != null)
                {
                    return _CreationTime.Value;
                }

                byte[] buffer = new byte[500];
                int result = ReadMeta(_CurrentHandle, "Exif.Photo.DateTimeOriginal", buffer, buffer.Length);

                if (result != 0)
                {
                    return DateTime.MinValue;
                }

                string t = ByteArrayToString(buffer);

                if (t.Length == 0)
                {
                    return new DateTime(0);
                }

                // Alle Felder sind mit Doppelpunkt bzw. Leerzeichen voneinander getrennt
                // Text auftrennen
                string[] teile = t.Split(new char[] { ':', ' ' });
                // und aus den einzelnen Feldern eine DateTime-Struktur erzeugen
                try
                {
                    _CreationTime = new DateTime(int.Parse(teile[0]), int.Parse(teile[1]), int.Parse(teile[2]), int.Parse(teile[3]), int.Parse(teile[4]), int.Parse(teile[5]));
                    return _CreationTime.Value;
                }
                catch
                {
                    _CreationTime = null;
                    return DateTime.MinValue;
                }
            }
            set
            {
                if (_CurrentHandle == 0)
                {
                    Debug.WriteLine("Handle is 0");
                    return;
                }

                string val = value.Year.ToString("0000") + ":";
                val += value.Month.ToString("00") + ":";
                val += value.Day.ToString("00") + " ";
                val += value.Hour.ToString("00") + ":";
                val += value.Minute.ToString("00") + ":";
                val += value.Second.ToString("00");

                int result = ModifyMeta(_CurrentHandle, "Exif.Photo.DateTimeOriginal", val, DllTypeId.asciiString);
                if (result != 0)
                {
                    Debug.WriteLine("Error while writing DateTimeOriginal");
                    _CreationTime = null;
                    return;
                }

                //  result = ModifyMeta(_CurrentHandle, "Iptc.Application2.DateCreated", val, DllTypeId.stringType);
                _CreationTime = value;
            }
        }
        /// <summary>
        /// Gets the creation Time
        /// </summary>
        public DateTime ChangedTime
        {
            get
            {
                if (_CurrentHandle == 0)
                {
                    return DateTime.MinValue;
                }
                if (_ChangedTime != null)
                {
                    return _ChangedTime.Value;
                }

                byte[] buffer = new byte[500];
                int result = ReadMeta(_CurrentHandle, "Exif.Image.DateTime", buffer, buffer.Length);

                if (result != 0)
                {
                    return DateTime.MinValue;
                }

                string t = ByteArrayToString(buffer);

                if (t.Length == 0)
                {
                    return new DateTime(0);
                }

                // Alle Felder sind mit Doppelpunkt bzw. Leerzeichen voneinander getrennt
                // Text auftrennen
                string[] teile = t.Split(new char[] { ':', ' ' });
                // und aus den einzelnen Feldern eine DateTime-Struktur erzeugen
                try
                {
                    _ChangedTime = new DateTime(int.Parse(teile[0]), int.Parse(teile[1]), int.Parse(teile[2]), int.Parse(teile[3]), int.Parse(teile[4]), int.Parse(teile[5]));
                    return _ChangedTime.Value;
                }
                catch
                {
                    _ChangedTime = null;
                    return DateTime.MinValue;
                }
            }
            set
            {
                if (_CurrentHandle == 0)
                {
                    Debug.WriteLine("Handle is 0");
                    return;
                }
                string val = value.Year.ToString("0000") + ":";
                val += value.Month.ToString("00") + ":";
                val += value.Day.ToString("00") + " ";
                val += value.Hour.ToString("00") + ":";
                val += value.Minute.ToString("00") + ":";
                val += value.Second.ToString("00");

                int result = ModifyMeta(_CurrentHandle, "Exif.Image.DateTime", val, DllTypeId.asciiString);
                if (result != 0)
                {
                    Debug.WriteLine("Error while writing DateTime");
                    _ChangedTime = null;
                    return;
                }
                _ChangedTime = value;
            }
        }
        /// <summary>
        /// Set / Gets the comments tag
        /// </summary>
        public string Comments
        {
            get
            {
                if (_Comments == null)
                {
                    _Comments = ReadExifData("Exif.Photo.UserComment");

                    // Some Exif tools like Exifer add the prefix "ASCII" to the comment (which is IMHO
                    // according to the standard), Exiv2 makes 'charset="Ascii"' out of it -> we cut this
                    // away
                    if (_Comments.StartsWith(_CommentAsciiPrefix))
                    {
                        _Comments = _Comments.Substring(_CommentAsciiPrefix.Length).Trim();
                    }
                }
                return _Comments;
            }
            set
            {
                if (_CurrentHandle != 0)
                {
                    _Comments = value;
                    int result = -1;
                    string exiftag = "Exif.Photo.UserComment";
                    if (value == null || value.Length == 0)
                    {
                        result = RemoveMeta(_CurrentHandle, exiftag);
                        Debug.WriteLineIf(result != 0, exiftag + " can not be removed");
                    }
                    else
                    {
                        result = ModifyMeta(_CurrentHandle, exiftag, value, DllTypeId.Comment);
                        Debug.WriteLineIf(result != 0, "Error while writing " + exiftag);
                    }
                }
            }
        }
        /// <summary>
        /// Sets / Gets the description tag
        /// </summary>
        public string Description
        {
            get
            {
                if (_Description == null)
                {
                    _Description = ReadExifData("Exif.Image.ImageDescription");
                }
                return _Description;
            }
            set
            {
                if (_CurrentHandle != 0)
                {
                    int result = -1;
                    string exiftag = "Exif.Image.ImageDescription";
                    _Description = value;
                    if (value == null || value.Length == 0)
                    {
                        result = RemoveMeta(_CurrentHandle, exiftag);
                        Debug.WriteLineIf(result != 0, exiftag + " can not be removed");
                    }
                    else
                    {
                        result = ModifyMeta(_CurrentHandle, exiftag, value, DllTypeId.asciiString);
                        Debug.WriteLineIf(result != 0, "Error while writing " + exiftag);
                    }
                }
            }
        }
        #endregion
        /// <summary>
        /// The default constructor
        /// </summary>
        public Exiv2()
        {
            if (_ImageBuffer == null)
            {
                _ImageBuffer = new byte[1000000];
            }
        }
        /// <summary>
        /// Constructor building an exiv-Wrapper object with a file
        /// </summary>
        /// <param name="fileName">Name of the file to load</param>
        public Exiv2(string fileName)
        {
            if (_ImageBuffer == null)
            {
                _ImageBuffer = new byte[1000000];
            }
            this.LoadImageData(fileName);
        }
        /// <summary>
        /// Loads the image data
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public bool LoadImageData(string fileName)
        {
            try
            {
                _CurrentHandle = OpenFileImage(fileName);
            }
            catch
            {
                Console.WriteLine(">>>>>>> " + fileName + " NOT loaded <<<<<<<<<<");
                return false;
            }

            FileInfo fi = new FileInfo(fileName);
            if (fi.Length > _ImageBuffer.Length)
            {
                _ImageBuffer = new byte[fi.Length + 1000];
            }
            Console.WriteLine(">>>>>>> " + fileName + " loaded");
            return (_CurrentHandle != 0);
        }
        /// <summary>
        /// Releases the Image data without saving
        /// </summary>
        public void ReleaseData()
        {
            if (_CurrentHandle != 0)
            {
                try
                {
                    FreeImage(_CurrentHandle);
                }
                catch
                {
                }
            }
            Console.WriteLine(">>>>>>> Data released");
            _CurrentHandle = 0;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool SaveImageData()
        {
            int result = -1;
            if (_CurrentHandle != 0)
            {
                try
                {
                    result = SaveImage(_CurrentHandle);
                }
                catch
                {
                    Console.WriteLine(">>>>>>> Data NOT saved <<<<<<<<<");
                    return false;
                }
            }
            Console.WriteLine(">>>>>>> Data saved");
            return (result == 0);
        }
        /// <summary>
        /// Converts the byte array to a string
        /// </summary>
        /// <param name="array">Array with the read data</param>
        /// <returns>String</returns>
        private string ByteArrayToString(byte[] array)
        {
            if (array == null || array.Length == 0)
            {
                return "";
            }

            string retVal = "";
            foreach (byte b in array)
            {
                if (b > 0)
                {
                    retVal += new string((char)b, 1);
                }
            }
            return retVal;
        }
        /// <summary>
        /// Returns an Exif String
        /// </summary>
        /// <param name="tag">String Tag</param>
        /// <returns></returns>
        private string ReadExifData(string tag)
        {
            if (_CurrentHandle == 0)
            {
                return "";
            }

            byte[] buffer = new byte[50000];
            int result = ReadMeta(_CurrentHandle, tag, buffer, buffer.Length);

            if (result != 0)
            {
                Console.WriteLine(tag + ": No Data");
                return "";
            }

            string t = ByteArrayToString(buffer).Trim();
            Console.WriteLine(tag + ": " + t + "#EOS");
            return t;
        }
        /// <summary>
        /// Creates a thumbnail out of the file name for this Wrapper object
        /// </summary>
        /// <param name="filename"></param>
        public void CreateThumb(string filename)
        {
            try
            {
                Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback);

                Image img = Image.FromFile(filename);

                int x = img.Width;
                int y = img.Height;
                int x2 = (int)(img.Width * (double)((double)120 / (double)img.Height));
                Image thumb = img.GetThumbnailImage(x2, 120, myCallback, IntPtr.Zero);
                Thumbnail = thumb;
                img.Dispose();
                //  thumb.Dispose();
            }
            catch
            {
                Console.WriteLine("Error while creating thumb");
            }
        }
        /// <summary>
        /// Static function for creating a thumb and returning it
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public Image CreateThumbnail(string filename)
        {
            try
            {
                Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback);

                Image img = Image.FromFile(filename);

                int x = img.Width;
                int y = img.Height;
                int x2 = (int)(img.Width * (double)((double)120 / (double)img.Height));
                Image thumb = img.GetThumbnailImage(x2, 120, myCallback, IntPtr.Zero);
                img.Dispose();
                return thumb;
                //  thumb.Dispose();
            }
            catch
            {
                Console.WriteLine("Error while creating thumb");
            }
            return null;
        }
        /// <summary>
        /// Dummy Callback
        /// </summary>
        /// <returns></returns>
        public bool ThumbnailCallback()
        {
            return false;
        }
    }
}
